﻿using System.Linq;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Reflection;
using System.Windows.Data;

namespace DeepEarth.Core.Persistence
{
	public static class Persist
	{
		#region public: Register() (in class constructors)

		public static void Register<T>(string propertyPath)
		{
			if (string.IsNullOrEmpty(propertyPath.Trim()))
				throw new ArgumentNullException();
			List<string> list;
			if (!properties.TryGetValue(typeof(T), out list))
				properties[typeof(T)] = list = new List<string>();
			list.Add(propertyPath);
		}

		#endregion

		#region Attached Properties

        #region Name
        public static string GetName(DependencyObject obj)
		{
			return (string)obj.GetValue(NameProperty);
		}

		public static void SetName(DependencyObject obj, string value)
		{
			obj.SetValue(NameProperty, value);
		}

		public static readonly DependencyProperty NameProperty =
			DependencyProperty.RegisterAttached("Name", typeof(string), typeof(Persist), new PropertyMetadata(PersistenceApplied));

        private static void PersistenceApplied(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            FrameworkElement element = sender as FrameworkElement;

            if (element != null)
            {
                string persistenceName = (string)args.NewValue;

                Type t = element.GetType();

                var persistedProperties = t.GetProperties().Where(p => p.GetCustomAttributes(typeof(PersistAttribute), false).Any()).ToList();

                element.Loaded += (o, e) =>
                    {
                        try
                        {
                            foreach (PropertyInfo p in persistedProperties)
                            {
                                string serialized = Persist.Storage[persistenceName, p.Name];

                                if (!string.IsNullOrEmpty(serialized))
                                {
                                    object value = JsonSerializer.Deserialize(serialized, p.PropertyType);

                                    p.SetValue(element, value, null);
                                }
                            }
                        }
                        catch
                        {
                        }
                    };

                Application.Current.Exit += (o, e) =>
                    {
                        try
                        {
                            foreach (PropertyInfo p in persistedProperties)
                            {
                                string serialized = JsonSerializer.Serialize(p.GetValue(element, null));

                                if (!string.IsNullOrEmpty(serialized))
                                {
                                    Persist.Storage[persistenceName, p.Name] = serialized;
                                }
                            }
                        }
                        catch
                        {
                        }
                    };
                    
            }
        }

        // Auto persist - trying different approach for now
		private static void NameChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if (d == null)
				return;

			string name = (string)e.NewValue;
			if (string.IsNullOrEmpty(name))
				return;

			List<string> list;
			if (!properties.TryGetValue(d.GetType(), out list))
				return;

			foreach (var path in list)
			{
				var pv = new PropertyValue
				{
					Name = name,
					PropertyPath = path,
					Target = d
				};
				pv.ReadValue();
				pv.Bind();

				// necessary or the value will be garbage collected!
				allvalues.Add(pv);
            }
        }

        #endregion


        static List<PropertyValue> allvalues = new List<PropertyValue>();

		#endregion

		#region Storage

		public static PersistStore Storage
		{
			get { return storage; }
		}
		static PersistStore storage;

#if !WINDOWS_PHONE
		static Persist()
		{
			storage = new PersistStore();
			storage.Load();
		}

#endif

		#endregion

		#region internals: PropertyValue

		class PropertyValue : DependencyObject
		{
			public string PropertyPath { get; set; }
			public string Name { get; set; }
			public DependencyObject Target { get; set; }

			#region Value

			public string Value
			{
				get
				{
					return (string)GetValue(ValueProperty);
				}

				set
				{
					SetValue(ValueProperty, value);
				}
			}

			/// <summary>
			/// Value dependency property.
			/// </summary>
			public static readonly DependencyProperty ValueProperty =
				DependencyProperty.Register(
					"Value",
					typeof(string),
					typeof(PropertyValue),
					new PropertyMetadata((d, e) => ((PropertyValue)d).OnValueChanged((string)e.OldValue, (string)e.NewValue)));

			/// <summary>
			/// handles the ValueProperty changes.
			/// </summary>
			/// <param name="oldValue">The old value.</param>
			/// <param name="newValue">The new value.</param>
			private void OnValueChanged(string oldValue, string newValue)
			{
				Storage[Name, PropertyPath] = newValue;
			}

			#endregion Value

			public void ReadValue()
			{
				var value = Storage[Name, PropertyPath];
				if (!string.IsNullOrEmpty(value))
					Value = value;
			}

			public void Bind()
			{
				string old = Value;

				var binding = new Binding(PropertyPath);
				binding.Source = Target;
				binding.Mode = BindingMode.TwoWay;
				BindingOperations.SetBinding(this, ValueProperty, binding);

				if (!string.IsNullOrEmpty(old.Trim()))
					Value = old;
			}
		}

		static Dictionary<Type, List<string>> properties = new Dictionary<Type, List<string>>();

		#endregion
	}
}
